package mirkofrancuski.battleship.game;

import java.util.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.LinkedList;

import android.database.sqlite.SQLiteDatabase;

import mirkofrancuski.battleship.db.IntTable;
import mirkofrancuski.battleship.db.ProbabilityMatrixTable;
import mirkofrancuski.battleship.game.Board.Direction;

public class ProbabilityMatrix {

	private final static int N = Board.BOARD_SIZE;

	private Board board;

	private int data[][];

	private LinkedList<Cell> shipsLeft;

	public ProbabilityMatrix(Board board) {
		super();
		this.board = board;
		data = new int[N][N];

		shipsLeft = new LinkedList<Cell>(Arrays.asList(Board.ships));
	}

	public void clear() {
		Arrays.fill(data[0], 0);
		for (int i = 1; i < N; i++)
			data[i] = Arrays.copyOf(data[0], Board.BOARD_SIZE);
	}

	public void calculateEmpty() {
		// calculate row 0
		calculate(new Coordinates(0, 0), new Coordinates(0, 9));

		for (int i = 1; i < N; i++)
			data[i] = Arrays.copyOf(data[0], N);

		// transpose and add
		this.add(this.transpose());
	}

	private void calculate(Coordinates start, Coordinates end) {
		if (start == end)
			return;

		if (start.horizontal == end.horizontal) {
			int n = end.vertical - start.vertical + 1;

			int x[] = new int[n + 1];
			for (int i = 0; i <= n; i++)
				x[i] = i + 1;

			for (Cell ship : shipsLeft) {
				int size = ship.getSize();
				if (size > n)
					continue;

				for (int i = start.horizontal, j = start.vertical, k = 0; j <= end.vertical; j++, k++) {
					if (size != n)
						data[i][j] += Math.min(size,
								Math.min(n + 1 - x[k], x[k]));
					else
						data[i][j] += 1;
				}
			}

		} else {
			int n = end.horizontal - start.horizontal + 1;

			int x[] = new int[n + 1];
			for (int i = 0; i <= n; i++)
				x[i] = i + 1;

			for (Cell ship : shipsLeft) {
				int size = ship.getSize();
				if (size > n)
					continue;

				for (int i = start.horizontal, j = start.vertical, k = 0; i <= end.horizontal; i++, k++) {
					if (size != n)
						data[i][j] += Math.min(size,
								Math.min(n + 1 - x[k], x[k]));
					else
						data[i][j] += 1;
				}
			}
		}
	}

	private void add(int[][] data) {
		for (int i = 0; i < N; i++)
			for (int j = 0; j < N; j++)
				this.data[i][j] += data[i][j];

	}

	private int[][] transpose() {
		int[][] data = new int[N][N];
		for (int i = 0; i < N; i++)
			for (int j = 0; j < N; j++)
				data[j][i] = this.data[i][j];
		return data;
	}

	public void recalculate() {
		clear();

		Coordinates start = new Coordinates();
		Coordinates end = new Coordinates();
		// horizontal
		for (int i = 0; i < N; i++) {
			start.horizontal = i;
			start.vertical = 0;
			end.horizontal = i;
			end.vertical = 0;
			for (int j = 0; j < N; j++) {
				end.vertical = j;
				if (!board.isEmpty(end)) {
					if (start.equals(end)) {
						start.vertical++;
						continue;
					}
					calculate(start, new Coordinates(i, j - 1));
					start.vertical = j + 1;
				}
			}
			calculate(start, end);
		}
		// vertical
		for (int j = 0; j < N; j++) {
			start.horizontal = 0;
			start.vertical = j;
			end.horizontal = 0;
			end.vertical = j;
			for (int i = 0; i < N; i++) {
				end.horizontal = i;
				if (!board.isEmpty(end)) {
					if (start.equals(end)) {
						start.horizontal++;
						continue;
					}
					calculate(start, new Coordinates(i - 1, j));
					start.horizontal = end.horizontal + 1;
				}
			}
			calculate(start, end);
		}

	}

	private boolean canBePlaced(Coordinates coordinates, Direction direction,
			int shipSize) {
		boolean empty = true;
		Coordinates coord = new Coordinates(coordinates);
		if (direction == Direction.HORIZONTAL) {
			for (int j = 0; j < shipSize && empty; j++) {
				empty = board.isEmpty(coord) || board.isHit(coord);
				coord.vertical++;
			}
		} else {
			for (int i = 0; i < shipSize && empty; i++) {
				empty = board.isEmpty(coord) || board.isHit(coord);
				coord.horizontal++;
			}
		}
		return empty;
	}

	public void calculateCell(Coordinates coord, int carry) {
		for (Cell ship : shipsLeft) {
			int shipSize = ship.getSize();
			calculateCellHorizontaly(coord, carry, shipSize);
			calculateCellVerticaly(coord, carry, shipSize);
			data[coord.horizontal][coord.vertical] = 0;
		}
	}

	public void calculateCellHorizontaly(Coordinates coordinates, int carry,
			int shipSize) {
		int i = coordinates.horizontal;
		int from = coordinates.vertical - shipSize + 1;
		int to = coordinates.vertical;

		Coordinates coord = new Coordinates(i, 0);
		for (int j = from; j <= to; j++) {
			coord.vertical = j;
			if (!canBePlaced(coord, Direction.HORIZONTAL, shipSize))
				continue;
			for (int k = 0; k < shipSize; k++)
				data[i][j + k] += carry;

		}
	}

	public void calculateCellVerticaly(Coordinates coordinates, int carry,
			int shipSize) {
		int j = coordinates.vertical;
		int from = coordinates.horizontal - shipSize + 1;
		int to = coordinates.horizontal;

		Coordinates coord = new Coordinates(0, j);
		for (int i = from; i <= to; i++) {
			coord.horizontal = i;
			if (!canBePlaced(coord, Direction.VERTICAL, shipSize))
				continue;
			for (int k = 0; k < shipSize; k++)
				data[i + k][j] += carry;

		}
	}

	public LinkedList<Coordinates> getMax() {
		LinkedList<Coordinates> listMax = new LinkedList<Coordinates>();

		Coordinates coord = new Coordinates();
		double max = -1;

		for (int i = 0; i < N; i++)
			for (int j = 0; j < N; j++)
				if (data[i][j] > max) {
					coord.horizontal = i;
					coord.vertical = j;
					if (!board.isEmpty(coord))
						continue;
					max = data[i][j];
					listMax.clear();
					listMax.add(new Coordinates(coord));
				} else if (data[i][j] == max) {
					coord.horizontal = i;
					coord.vertical = j;
					if (!board.isEmpty(coord))
						continue;
					listMax.add(new Coordinates(coord));
				}

		return listMax;
	}

	public LinkedList<Coordinates> getMaxSurrounding(
			LinkedList<Coordinates> list) {
		double max = -1;
		LinkedList<Coordinates> listMax = new LinkedList<Coordinates>();

		for (Coordinates coord : list) {
			int i = coord.horizontal;
			int j = coord.vertical;

			if (data[i][j] > max) {
				coord.horizontal = i;
				coord.vertical = j;
				if (!board.isEmpty(coord))
					continue;
				max = data[i][j];
				listMax.clear();
				listMax.add(new Coordinates(coord));
			} else if (data[i][j] == max) {
				coord.horizontal = i;
				coord.vertical = j;
				if (!board.isEmpty(coord))
					continue;
				listMax.add(new Coordinates(coord));
			}

		}
		return listMax;
	}

	public void updateShips(Cell cell) {
		shipsLeft.remove(cell);
	}

	public boolean noShipsLeft() {
		return shipsLeft.isEmpty();
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		Formatter formatter = new Formatter(sb);
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++)
				formatter.format("%3d ", data[i][j]);
			formatter.flush();
			sb.append('\n');
		}
		formatter.close();
		sb.append('\n');
		return sb.toString();
	}

	public void save(SQLiteDatabase db) {

		String className = ProbabilityMatrix.class.toString();

		ProbabilityMatrixTable.saveProbabilityMatrix(db, data);

		for (Cell cell : shipsLeft) {
			IntTable.addInt(db, className, cell.name(), cell.ordinal());
		}
	}

	public void restore(SQLiteDatabase db) {
		String className = ProbabilityMatrix.class.toString();

		data = ProbabilityMatrixTable.restoreProbabilityMatrix(db);

		shipsLeft.clear();
		HashMap<String, Integer> hashMap = IntTable.getInts(db, className);
		for (Cell cell : Board.ships) {
			Integer cellOrdinal = hashMap.get(cell.name());
			if (cellOrdinal != null) {
				shipsLeft.add(Cell.getCell(cellOrdinal));
			}
		}

	}

}
